package com.wfsc.web.util;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.misc.BASE64Decoder;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.util.Base64;

public class SM4 {

    static {
        try {
            Security.addProvider(new BouncyCastleProvider());
        } catch (Exception e) {
        }
    }

    /**
     * 秘钥空间大小.
     */
    public static final int SM4_KEY_SIZE = 128;

    /**
     * 默认秘钥空间为128，Key的长度是16.
     */
    public static final int SM4_KEY_LENGTH = 16;

    /**
     * 算法编号.
     */
    public static final String SM4_NAME = "SM4";

    /**
     * CBC模式串.
     */
    public static final String SM4_NAME_ECB = "SM4/CBC/PKCS5Padding";

    /**
     * 首次加密初始向量.
     */
    protected static final byte[] SM4_KEY_IV = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 };

    /**
     * 生成SM4算法的KEY.
     *
     * @return 生成的SM4秘钥.
     * @throws Exception .
     */
    public String generateSm4Key() {
        try {
            return Base64.getEncoder().encodeToString(generateSm4Key(SM4_KEY_SIZE));
        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
        }
        return null;
    }

    /**
     * 生成SM4算法的KEY.
     *
     * @param sm4Key 指定秘钥空间大小.
     * @return 生成的SM4秘钥.
     * @throws Exception .
     */
    private static byte[] generateSm4Key(int sm4Key) throws NoSuchAlgorithmException, NoSuchProviderException {

        KeyGenerator keyGenerator = KeyGenerator.getInstance(SM4_NAME, BouncyCastleProvider.PROVIDER_NAME);
        keyGenerator.init(sm4Key, new SecureRandom());
        return keyGenerator.generateKey().getEncoded();
    }

    /**
     * 对文本内容进行加密.
     *
     * @param plainText 待加密明文内容.
     * @param sm4Key    SM4秘钥.
     * @return 加密的密文.
     */
    public String encodeText(String plainText, String sm4Key) {
        try {
            return encodeByCbc(plainText, sm4Key);
        } catch (NoSuchProviderException | BadPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException
                | NoSuchPaddingException e) {
        }
        return null;
    }

    /**
     * 对文本密文进行解密.
     *
     * @param cipherText 待解密密文.
     * @param sm4Key     SM4秘钥.
     * @return 解密的明文.
     * @throws Exception .
     */
    public String decodeText(String cipherText, String sm4Key) {
        return decodeByCbc(cipherText, sm4Key);
    }

    /**
     * 对字节数组内容进行加密.
     *
     * @param sm4Key SM4秘钥.
     * @return 加密的密文.
     */
    public byte[] encodeBytes(byte[] plainBytes, String sm4Key) {
        byte[] sm4KeyBytes = null;
        byte[] cipherBytes = null;

        try {
            // 秘钥位数处理转换.
            sm4Key = sm4KeyPadding(sm4Key);
            // base64格式秘钥转换：sm4Key to byte[].
            sm4KeyBytes = Base64.getDecoder().decode(sm4Key);
            // 使用转换后的原文和秘钥进行加密操作.
            cipherBytes = encodeCbcPadding(plainBytes, sm4KeyBytes);
            // 对加密结果使用base64进行编码：cipherBytes to Base64格式.
        } catch (Exception ex) {
            cipherBytes = new byte[] { 1 };
        }
        return cipherBytes;
    }

    /**
     * 对字节数组密文进行解密.
     *
     * @param sm4Key SM4秘钥.
     * @return 解密的明文.
     */
    public byte[] decodeBytes(byte[] cipherBytes, String sm4Key) {
        byte[] keyBts = null;
        byte[] plainBytes = new byte[0];

        try {
            // 秘钥位数处理转换.
            sm4Key = sm4KeyPadding(sm4Key);
            // base64格式秘钥转换：sm4Key to byte[].
            keyBts = Base64.getDecoder().decode(sm4Key);
            // 使用转换后的密文和秘钥进行解密操作
            plainBytes = decryptCbcPadding(cipherBytes, keyBts);
        } catch (Exception ex) {
            plainBytes = new byte[] { 0 };
        }
        return plainBytes;
    }

    /**
     * 基于CBC模式进行SM4加密.
     *
     * @param plainText 待加密明文.
     * @param sm4Key    Base64格式秘钥.
     * @return 加密后Base64格式密文.
     * @throws Exception 可能异常.
     */
    private String encodeByCbc(String plainText, String sm4Key) throws NoSuchProviderException, BadPaddingException,
            NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException {
        String cipherText = "";
        byte[] sm4KeyBytes = null;
        byte[] plainBytes = null;
        byte[] cipherBytes = null;

        try {
            // 秘钥位数处理转换.
            sm4Key = sm4KeyPadding(sm4Key);

            // base64格式秘钥转换：sm4Key to byte[].
            sm4KeyBytes = Base64.getDecoder().decode(sm4Key);
            // String格式原文转换：plainText to byte[].
            plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
            // 使用转换后的原文和秘钥进行加密操作.
            cipherBytes = encodeCbcPadding(plainBytes, sm4KeyBytes);
            // 对加密结果使用base64进行编码：cipherBytes to Base64格式.
            cipherText = Base64.getEncoder().encodeToString(cipherBytes);
        } catch (RuntimeException ex) {
            cipherText = "";
        }
        return cipherText;
    }

    /**
     * SM4算法的CBC模式加密.
     *
     * @param plainBytes 待加密明文.
     * @param sm4Key     Base64格式秘钥.
     * @return 加密后byte[]格式密文.
     * @throws Exception 可能异常.
     */
    private byte[] encodeCbcPadding(byte[] plainBytes, byte[] sm4Key) throws NoSuchPaddingException,
            NoSuchAlgorithmException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = generateSm4EcbCipher(SM4_NAME_ECB, Cipher.ENCRYPT_MODE, sm4Key);
        return cipher.doFinal(plainBytes);
    }

    /**
     * 基于CBC模式进行SM4解密.
     *
     * @param cipherText 待解密密文.
     * @param sm4Key     Base64格式秘钥.
     * @return 解密后原文.
     * @throws Exception 可能异常.
     */
    private String decodeByCbc(String cipherText, String sm4Key) {
        String plainText = "";
        byte[] keyBts = null;
        byte[] cipherBts = null;
        byte[] plainBytes = null;

        // 使用转换后的密文和秘钥进行解密操作
        try {
            // 秘钥位数处理转换.
            sm4Key = sm4KeyPadding(sm4Key);
            // base64格式秘钥转换：sm4Key to byte[].
            BASE64Decoder decoder = new BASE64Decoder();
            // sm4KeyBytes =
            // Base64.getDecoder().decode(sm4Key.getBytes(StandardCharsets.UTF_8));
            keyBts = decoder.decodeBuffer(sm4Key);
            // keyBts = Base64.getDecoder().decode(sm4Key);
            // base64格式密文转换：cipherText to byte[].
            cipherBts = decoder.decodeBuffer(cipherText);
            plainBytes = decryptCbcPadding(cipherBts, keyBts);
        } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | BadPaddingException
                | IllegalBlockSizeException | IOException e) {
            e.printStackTrace();
        }
        // 将解密结果转换为字符串：srcData to String.
        plainText = new String(plainBytes, StandardCharsets.UTF_8);
        return plainText;
    }

    /**
     * SM4算法的CBC模式解密.
     *
     * @param sm4Key Base64格式秘钥.
     * @return 解密后byte[]格式密文.
     * @throws Exception 可能异常.
     */
    private byte[] decryptCbcPadding(byte[] cipherBytes, byte[] sm4Key) throws NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {

        Cipher cipher = generateSm4EcbCipher(SM4_NAME_ECB, Cipher.DECRYPT_MODE, sm4Key);
        return cipher.doFinal(cipherBytes);
    }

    /**
     * 针对错误的秘钥进行补齐或除余操作.
     *
     * @param sm4Key Base64格式秘钥.
     * @return 补齐或除余后的结果.
     */
    private static String sm4KeyPadding(String sm4Key) {
        String targetSm4Key = null;
        byte[] sm4KeyBytes = null;
        byte[] targetSm4KeyBts = null;

        try {
            if (null == sm4Key) {
                targetSm4Key = "";
                return targetSm4Key;
            }

            BASE64Decoder decoder = new BASE64Decoder();
            sm4KeyBytes = decoder.decodeBuffer(sm4Key);

            String decodedString = new String(sm4KeyBytes, "UTF-8");

            // 若Key超长，则除去多余的内容.
            if (sm4KeyBytes.length > SM4_KEY_LENGTH) {
                targetSm4KeyBts = new byte[SM4_KEY_LENGTH];
                System.arraycopy(sm4KeyBytes, 0, targetSm4KeyBts, 0, SM4_KEY_LENGTH);

            }
            // 若Key较短，则补齐多余的内容.
            else if (sm4KeyBytes.length < SM4_KEY_LENGTH) {
                targetSm4KeyBts = new byte[SM4_KEY_LENGTH];
                System.arraycopy(sm4KeyBytes, 0, targetSm4KeyBts, 0, sm4KeyBytes.length);
                Arrays.fill(targetSm4KeyBts, sm4KeyBytes.length, SM4_KEY_LENGTH, (byte) 1);
            } else {
                targetSm4KeyBts = sm4KeyBytes;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            // log.error("SM4Util.sm4KeyPadding.", ex);
            targetSm4KeyBts = new byte[0];
        }

        // 以Base64格式返回Key.
        return Base64.getEncoder().encodeToString(targetSm4KeyBts);
    }

    /**
     * 生成SM4算法实例.
     *
     * @param sm4Name 算法名称.
     * @param sm4Mode 加密模式.
     * @param sm4Key  秘钥内容.
     * @return SM4算法实例.
     * @throws Exception 可能异常.
     */
    private Cipher generateSm4EcbCipher(String sm4Name, int sm4Mode, byte[] sm4Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {

        Cipher cipher = Cipher.getInstance(sm4Name, BouncyCastleProvider.PROVIDER_NAME);
        Key secretKey = new SecretKeySpec(sm4Key, SM4_NAME);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(SM4_KEY_IV);
        try {
            cipher.init(sm4Mode, secretKey, ivParameterSpec);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        return cipher;
    }

    // public static void main(String[] args) {
    // String data = "{\n" +
    // " \"entId\": \"0b094b92804a4a3aa6f611b863f4080f\",\n" +
    // " \"wasteId\": \"34a9849ce7d14358915325f94bd0c61c\",\n" +
    // " \"quantity\": 0.003 ,\n" +
    // " \"unit\": \"吨\",\n" +
    // " \"sourceId\": \"a238ef2e6f744944ba1ab9a69bbcadd5\",\n" +
    // " \"produceDate\": \"2021-07-01 01:02:02\",\n" +
    // " \"userName\": \"测试用户0714\",\n" +
    // " \"iotToken\": \"a238ef2e6f744944ba1ab9a69bbcadd5\",\n" +
    // " \"remark\": \"测试数据\"\n" +
    // " }";
    // String key = "903B2CBD-D669-47E3-A6D9-94655C72B3BB";
    // TestEncrypt encrypt = new TestEncrypt();
    // String encryptData = encrypt.encodeText(data, key);
    //
    // String plainText = encrypt.decodeText(encryptData, key);
    //
    // }

    // public static void main(String[] args) {

    //
    // =========================");

    // String data = "123";
    // String key = "903B2CBD-D669-47E3-A6D9-94655C72B3BB";
    // TestEncrypt encrypt = new TestEncrypt();
    // String encryptData = encrypt.encodeText(data, key);
    //

    // }
}
